#include <memory/host.h>
#include <memory/paddr.h>
#include <isa.h>
#include <device/device.h>
#include <trace/mtrace.h>

extern void difftest_skip_ref();
#ifdef CONFIG_PMEM_MALLOC
static byte_t *pmem = NULL;
#else // CONFIG_PMEM_GARRAY
static byte_t pmem[CONFIG_MSIZE] PG_ALIGN = {};
#endif

#ifdef CONFIG_DMEM_MALLOC
static byte_t *dmem = NULL;
#else // CONFIG_DMEM_GARRAY
static byte_t dmem[CONFIG_DSIZE] PG_ALIGN = {};
#endif

void detect_device(word_t data,const char* devname,int type){
	const char* flag = type ? "WRITE" : "READ" ;
	printf("<DEV-%s> data = " FMT_WORD " for device(%s)\n",flag,data,devname);
}

byte_t* paddr_to_ptr(paddr_t paddr){
	return pmem + (paddr - PMEM_LEFT);
}

paddr_t ptr_to_paddr(byte_t* ptr){
	return (ptr - pmem) + PMEM_LEFT;
}


byte_t* ioaddr_to_ptr(ioaddr_t ioaddr){
	return dmem + (ioaddr - DMEM_LEFT);
}

ioaddr_t ptr_to_ioaddr(byte_t* ptr){
	return (ptr - dmem) + DMEM_LEFT;
}



static void out_pmem(paddr_t addr,int type){
	const char* flag = type ? "MEMORY-WRITE" : "MEMORY-READ";
	Assert(0,"<%s> addr = " FMT_PADDR " is out of bound of pmem [" FMT_PADDR "," FMT_PADDR "] at pc = " FMT_WORD, flag, addr, PMEM_LEFT, PMEM_RIGHT, cpu_data.pc);
}

static void out_dmem(paddr_t addr,int type){
	const char* flag = type ? "DEVICE-WRITE" : "DEVICE-READ";
	Assert(0,"<%s> addr = " FMT_IOADDR " is out of bound of dmem [" FMT_IOADDR "," FMT_IOADDR "] at pc = " FMT_WORD, flag, addr, DMEM_LEFT, DMEM_RIGHT, cpu_data.pc);
}



word_t device_read(ioaddr_t addr, int len){
	IFDEF(CONFIG_DIFFTEST_COND,difftest_skip_ref());
	if (addr==SERIAL_PORT){
		IFDEF(CONFIG_DTRACE,detect_device(0,"serial(NotSupport)",0));
		return 0;//TODO:这里有隐藏bug，写一次串口前会发生读一次串口的现象
	}else if(addr==RTC_ADDR){
		uint32_t data = get_time();
		IFDEF(CONFIG_DTRACE,detect_device(data,"timer",0));
		return data;
	}else if(addr==RTC_ADDR+4){
		uint32_t data = (uint64_t)get_time()>>32;
		IFDEF(CONFIG_DTRACE,detect_device(data,"timer",0));
		return data;
	}else if(addr==KBD_ADDR){
		uint32_t data = key_dequeue();
		IFDEF(CONFIG_DTRACE,detect_device(data,"keyboard",0));
		return data;
	}else if(addr>=VGACTL_ADDR && addr<=VGACTL_ADDR+4){
		uint32_t data = ptr_read(ioaddr_to_ptr(addr),len);
		IFDEF(CONFIG_DTRACE,detect_device(data,"vga_control",0));
		return data;
	}else if(addr>=FB_ADDR && addr<=FB_ADDR+screen_size()){
		uint32_t data = ptr_read(ioaddr_to_ptr(addr),len);
		IFDEF(CONFIG_DTRACE,detect_device(data,"vga_canvas",0));
		return data;
	}



	out_dmem(addr,0);	
	return 0;
}

void device_write(ioaddr_t addr, int len, word_t data){
	IFDEF(CONFIG_DIFFTEST_COND,difftest_skip_ref());
	if(addr==SERIAL_PORT){
		IFDEF(CONFIG_DTRACE,detect_device(data,"serial",1));
		putchar(data);
		return;
	}else if(addr==RTC_ADDR || addr==RTC_ADDR+4){
		IFDEF(CONFIG_DTRACE,detect_device(data,"timer",1));
		ptr_write(ioaddr_to_ptr(addr),len,data);
		return;
	}else if(addr==KBD_ADDR){
		IFDEF(CONFIG_DTRACE,detect_device(0,"keyboard(NotSupport)",1));
		return;
	}else if(addr>=VGACTL_ADDR && addr<=VGACTL_ADDR+4){
		IFDEF(CONFIG_DTRACE,detect_device(data,"vga_control",1));
		ptr_write(ioaddr_to_ptr(addr),len,data);
		return;
	}else if(addr>=FB_ADDR && addr<=FB_ADDR+screen_size()){
		IFDEF(CONFIG_DTRACE,detect_device(data,"vga_canvas",1));
		ptr_write(ioaddr_to_ptr(addr),len,data);
		return;
	}



	out_dmem(addr,1);
	return;
}


word_t paddr_read(paddr_t addr, int len){
	if(likely(in_pmem(addr))){
#ifdef CONFIG_MTRACE_COND
		word_t data = ptr_read(paddr_to_ptr(addr),len);
		mem_buffer_insert(0,cpu_data.pc,addr,data,len);
		return data;
#else
		return ptr_read(paddr_to_ptr(addr),len);
#endif

	}
	if(addr >= DEVICE_BASE){
#ifdef CONFIG_MTRACE_COND
		word_t data = device_read(addr,len);
		mem_buffer_insert(0,cpu_data.pc,addr,data,len);
		return data;
#else
		return device_read(addr,len);
#endif
	}
	
	out_pmem(addr,0);
	return 0;
}

void paddr_write(paddr_t addr, int len, word_t data){
	if(likely(in_pmem(addr))){
#ifdef CONFIG_MTRACE_COND
		ptr_write(paddr_to_ptr(addr),len,data);
		mem_buffer_insert(1,cpu_data.pc,addr,data,len);
		return;
#else
		ptr_write(paddr_to_ptr(addr),len,data);
		return;
#endif

	}
	if(addr >= DEVICE_BASE){
#ifdef CONFIG_MTRACE_COND
		device_write(addr,len,data);
		mem_buffer_insert(1,cpu_data.pc,addr,data,len);
		return;
#else
		device_write(addr,len,data);
		return;
#endif
	}

	out_pmem(addr,1);
	return;
}


void mem_init(){
	Log("memory initializing %s", ANSI_FMT("START", ANSI_FG_GREEN));
	#ifdef CONFIG_PMEM_MALLOC
	pmem = (byte_t*)malloc(CONFIG_MSIZE);
	assert(pmem);
	Log("memory initializing %s\n", ANSI_FMT("FINISH", ANSI_FG_GREEN));
	#endif


	Log("device memory initializing %s", ANSI_FMT("START", ANSI_FG_GREEN));
	#ifdef CONFIG_PMEM_MALLOC
	dmem = (byte_t*)malloc(CONFIG_DSIZE);
	assert(dmem);
	Log("device memory initializing %s\n", ANSI_FMT("FINISH", ANSI_FG_GREEN));
	#endif

}
